﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using Seagull.Configuration;
using Seagull.DAL.Utils;
using Seagull.Events;
using Seagull.Generic;
using Seagull.Model;
using Seagull.Model.Alerts;

namespace Seagull.DAL
{
    // TODO: refactor
    [DebuggerDisplay("{ToString()}")]
    public class Segment : IEquatable<Segment>
    {
        private const int MaxHour = 23;
        private const int MinHour = 0;

        public const int FrameCount = FrameSet.DefaultFrameCount * 3600;
        private static readonly Lazy<Dictionary<byte, PhaseShifter>> _channelDefaultPhaseShifters;

        static Segment()
        {
            _channelDefaultPhaseShifters = new Lazy<Dictionary<byte, PhaseShifter>>(() =>
                Config.Instance.ChannelConfig.ChannelNumbers
                    .ToDictionary(i => (byte)i, i => GetChannelDefaultPhaseShifter((byte)i)));
        }

        private readonly Lazy<PhaseShifter> _phaseShifter;

        private readonly FrameDataCoercer _channelNoiseFilter;

        private static readonly ConcurrentDictionary<byte, Segment> _currentSegmentCache
            = new ConcurrentDictionary<byte, Segment>(/*Config.Instance.Channels.Count*/);

        public Segment(byte channel, DateTime date, int hour)
            : this(channel, date.Date.AddHours(hour))
        {
        }

        /// <summary>
        /// 创建一个具有指定通道和起始时间的Segment. 起始时间会被截断到小时。
        /// </summary>
        /// <param name="channel"></param>
        /// <param name="startTime"></param>
        public Segment(byte channel, DateTime startTime)
        {
            Channel = channel;
            StartTime = startTime.TruncateToHour();
            _phaseShifter = new Lazy<PhaseShifter>(GetPhaseShifter);
            _channelNoiseFilter = GetChannelNoiseFilter();
        }

        public byte Channel { get; private set; }
        public DateTime StartTime { get; private set; }

        public DateTime Date
        {
            get { return StartTime.Date; }
        }

        public int Hour
        {
            get { return StartTime.Hour; }
        }

        public bool IsCurrent
        {
            get { return Hour == DateTime.Now.Hour; }
        }

        /// <summary>
        /// 取得该Segment的<see cref="PhaseShifter"/>. 
        /// 如果失败则返回通道默认的<see cref="DAL.PhaseShifter"/>.
        /// </summary>
        public PhaseShifter PhaseShifter
        {
            get { return _phaseShifter.Value; }
        }

        public FrameDataCoercer ChannelNoiseFilter
        {
            get { return _channelNoiseFilter; }
        }

        /// <summary>
        /// 取得同一个通道逻辑上的下一个Segment.
        /// </summary>
        public Segment Next
        {
            get
            {
                return Hour < MaxHour
                    ? new Segment(Channel, Date, Hour + 1)
                    : new Segment(Channel, Date.AddDays(1), MinHour);
            }
        }

        private PhaseShifter GetPhaseShifter()
        {
            try
            {
                var frameReader = new FrameReader(OpenPhaseFile());
                using(frameReader)
                {
                    var channelPhaseOffset = 
                        Config.Instance.ChannelConfig.HasChannel(Channel) ?
                        Config.Instance.ChannelConfig[Channel].Offset : 0;
                    return new PhaseShifter(channelPhaseOffset,
                        frameReader.ReadToEnd().Select(f => f.Data));
                }
            }
            catch(IOException exception)
            {
                AlertEvent.Instance.Publish(new ApplicationAlert(exception, "确定相位"));
                return _channelDefaultPhaseShifters.Value[Channel];
            }
        }

        private FrameDataCoercer GetChannelNoiseFilter()
        {
            var noiseQuota = Config.Instance.ChannelConfig.HasChannel(Channel) ? 
                Config.Instance.ChannelConfig[Channel].NoiseQuota : 0;
            return new FrameDataNoiseFilter(noiseQuota, 
                Config.Instance.DataReadConfig.MinDataValue);
        }

        private FileStream OpenPhaseFile()
        {
            return FileHelper.Open(() => File.OpenRead(PhaseFileFullName));
        }

        private string PhaseFileFullName
        {
            get
            {
                return Path.Combine(
                    Config.Instance.PathConfig.SourceDataFolder,
                    PathHelper.GetRelativeSegmentFileName(
                        StartTime, Model.Channel.PhaseChannel,
                        Config.Instance.PathConfig.DataFileExtension));
            }
        }

        private static PhaseShifter GetChannelDefaultPhaseShifter(byte channel)
        {
            return new PhaseShifter(Config.Instance.ChannelConfig[channel].Offset, 0);
        }

        public bool Contains(DateTime time)
        {
            return time >= StartTime && time < StartTime.AddHours(1);
        }

        public override string ToString()
        {
            return string.Format("Channel: {0}, StartTime: {1}, RelativePath: {2}",
                Channel, StartTime, PathHelper.GetRelativeSegmentFileName(this, string.Empty));
        }

        #region IEquatable<Segment> Members

        public bool Equals(Segment other)
        {
            if(ReferenceEquals(null, other))
                return false;
            if(ReferenceEquals(this, other))
                return true;
            return other.Channel == Channel && other.StartTime.Equals(StartTime);
        }

        public override bool Equals(object obj)
        {
            if(ReferenceEquals(null, obj))
                return false;
            if(ReferenceEquals(this, obj))
                return true;
            if(obj.GetType() != typeof(Segment))
                return false;
            return Equals((Segment)obj);
        }

        public override int GetHashCode()
        {
            unchecked
            {
                return (Channel.GetHashCode() * 397) ^ StartTime.GetHashCode();
            }
        }

        public static bool operator ==(Segment left, Segment right)
        {
            return Equals(left, right);
        }

        public static bool operator !=(Segment left, Segment right)
        {
            return !Equals(left, right);
        }

        #endregion
    }
}